import { DataProvider as IDataProvider, HttpError } from "@refinedev/core";
import { AxiosInstance } from "axios";
import { stringify } from "qs";
import {
    axiosInstance,
    generateFilter,
    generateSort,
    normalizeData,
    transformHttpError,
} from "./utils";

export const DataProvider = (
    apiUrl: string,
    httpClient: AxiosInstance = axiosInstance,
): Required<IDataProvider> => ({
    getList: async ({ resource, pagination, filters, sorters, meta }) => {
        const url = `${apiUrl}/${resource}`;

        const {
            current = 1,
            pageSize = 10,
            mode = "server",
        } = pagination ?? {};

        const locale = meta?.locale;
        const fields = meta?.fields;
        const populate = meta?.populate;
        const publicationState = meta?.publicationState;

        const querySorters = generateSort(sorters);
        const queryFilters = generateFilter(filters);

        const query = {
            ...(mode === "server"
                ? {
                      "pagination[page]": current,
                      "pagination[pageSize]": pageSize,
                  }
                : {}),
            locale,
            publicationState,
            fields,
            populate,
            sort: querySorters.length > 0 ? querySorters.join(",") : undefined,
        };

        const { data } = await httpClient.get(
            `${url}?${stringify(query, {
                encodeValuesOnly: true,
            })}&${queryFilters}`,
        );

        return {
            data: normalizeData(data),
            // added to support pagination on client side when using endpoints that provide only data (see https://github.com/refinedev/refine/issues/2028)
            total: data.meta?.pagination?.total || normalizeData(data)?.length,
        };
    },

    getMany: async ({ resource, ids, meta }) => {
        const url = `${apiUrl}/${resource}`;

        const locale = meta?.locale;
        const fields = meta?.fields;
        const populate = meta?.populate;
        const publicationState = meta?.publicationState;

        const queryFilters = generateFilter([
            {
                field: "id",
                operator: "in",
                value: ids,
            },
        ]);

        const query = {
            locale,
            fields,
            populate,
            publicationState,
            "pagination[pageSize]": ids.length,
        };

        const { data } = await httpClient.get(
            `${url}?${stringify(query, {
                encodeValuesOnly: true,
            })}&${queryFilters}`,
        );

        return {
            data: normalizeData(data),
        };
    },

    create: async ({ resource, variables }) => {
        const url = `${apiUrl}/${resource}`;

        let dataVariables: any = { data: variables };

        if (resource === "users") {
            dataVariables = variables;
        }

        try {
            const { data } = await httpClient.post(url, dataVariables);
            return {
                data,
            };
        } catch (error) {
            const httpError = transformHttpError(error);

            throw httpError;
        }
    },

    update: async ({ resource, id, variables }) => {
        const url = `${apiUrl}/${resource}/${id}`;

        let dataVariables: any = { data: variables };

        if (resource === "users") {
            dataVariables = variables;
        }

        try {
            const { data } = await httpClient.put(url, dataVariables);
            return {
                data,
            };
        } catch (error) {
            const httpError = transformHttpError(error);

            throw httpError;
        }
    },

    updateMany: async ({ resource, ids, variables }) => {
        const errors: HttpError[] = [];

        const response = await Promise.all(
            ids.map(async (id) => {
                const url = `${apiUrl}/${resource}/${id}`;

                let dataVariables: any = { data: variables };

                if (resource === "users") {
                    dataVariables = variables;
                }

                try {
                    const { data } = await httpClient.put(url, dataVariables);
                    return data;
                } catch (error) {
                    const httpError = transformHttpError(error);

                    errors.push(httpError);
                }
            }),
        );

        if (errors.length > 0) {
            throw errors;
        }

        return { data: response };
    },

    createMany: async ({ resource, variables }) => {
        const errors: HttpError[] = [];

        const response = await Promise.all(
            variables.map(async (param) => {
                try {
                    const { data } = await httpClient.post(
                        `${apiUrl}/${resource}`,
                        {
                            data: param,
                        },
                    );
                    return data;
                } catch (error) {
                    const httpError = transformHttpError(error);

                    errors.push(httpError);
                }
            }),
        );

        if (errors.length > 0) {
            throw errors;
        }

        return { data: response };
    },

    getOne: async ({ resource, id, meta }) => {
        const locale = meta?.locale;
        const fields = meta?.fields;
        const populate = meta?.populate;

        const query = {
            locale,
            fields,
            populate,
        };

        const url = `${apiUrl}/${resource}/${id}?${stringify(query, {
            encode: false,
        })}`;

        const { data } = await httpClient.get(url);

        return {
            data: normalizeData(data),
        };
    },

    deleteOne: async ({ resource, id }) => {
        const url = `${apiUrl}/${resource}/${id}`;

        const { data } = await httpClient.delete(url);

        return {
            data,
        };
    },

    deleteMany: async ({ resource, ids }) => {
        const response = await Promise.all(
            ids.map(async (id) => {
                const { data } = await httpClient.delete(
                    `${apiUrl}/${resource}/${id}`,
                );
                return data;
            }),
        );
        return { data: response };
    },

    getApiUrl: () => {
        return apiUrl;
    },

    custom: async ({
        url,
        method,
        filters,
        sorters,
        payload,
        query,
        headers,
    }) => {
        let requestUrl = `${url}?`;

        if (sorters) {
            const sortQuery = generateSort(sorters);
            if (sortQuery.length > 0) {
                requestUrl = `${requestUrl}&${stringify({
                    sort: sortQuery.join(","),
                })}`;
            }
        }

        if (filters) {
            const filterQuery = generateFilter(filters);
            requestUrl = `${requestUrl}&${filterQuery}`;
        }

        if (query) {
            requestUrl = `${requestUrl}&${stringify(query)}`;
        }

        let axiosResponse;
        switch (method) {
            case "put":
            case "post":
            case "patch":
                axiosResponse = await httpClient[method](url, payload, {
                    headers,
                });
                break;
            case "delete":
                axiosResponse = await httpClient.delete(url, {
                    data: payload,
                    headers: headers,
                });
                break;
            default:
                axiosResponse = await httpClient.get(requestUrl, { headers });
                break;
        }

        const { data } = axiosResponse;

        return Promise.resolve({ data });
    },
});
